from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException, WebDriverException, ElementClickInterceptedException, \
    StaleElementReferenceException
from selenium.common.exceptions import NoSuchElementException
from datetime import datetime
import time
import random
import sys
import logging
from webdriver_manager.chrome import ChromeDriverManager
course1 = 1

# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    filename='zhihuishu_auto.log'
)
console = logging.StreamHandler()
console.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
console.setFormatter(formatter)
logging.getLogger('').addHandler(console)


class ZhihuishuAutoPlayer:
    def __init__(self, username, password):
        """
        初始化智慧树自动播放器

        参数:
        username (str): 智慧树账号
        password (str): 智慧树密码
        """
        self.username = username
        self.password = password
        self.driver = webdriver.Chrome()
        self.total_videos = 0
        self.completed_videos = 0
        self.current_video_title = ""

    def init_driver(self):
        """初始化浏览器驱动"""
        options = webdriver.ChromeOptions()
        # 取消浏览器受控制提示
        options.add_experimental_option("excludeSwitches", ["enable-automation"])
        # 禁用浏览器扩展
        options.add_argument("--disable-extensions")
        # 取消密码保存提示
        prefs = {
            "credentials_enable_service": False,
            "profile.password_manager_enabled": False
        }
        options.add_experimental_option("prefs", prefs)

        try:
            # 使用Service类初始化ChromeDriver (Selenium 4.10.0+)
            service = Service(ChromeDriverManager().install())
            self.driver = webdriver.Chrome(service=service, options=options)
            self.driver.maximize_window()
            logging.info("浏览器初始化成功")
        except Exception as e:
            logging.error(f"浏览器初始化失败: {e}")
            sys.exit(1)

    def login(self):
        """登录智慧树平台"""
        try:
            # 打开智慧树登录页面
            self.driver.get(
                "https://passport.zhihuishu.com/login?service=https://onlineservice.zhihuishu.com/login/gologin")
            logging.info("打开登录页面")

            # 等待页面加载
            time.sleep(3)

            # 切换到账号密码登录
            try:
                password_login_btn = WebDriverWait(self.driver, 2).until(
                    EC.element_to_be_clickable((By.XPATH, '//div[@class="tab-title"]/span[2]'))
                )
                password_login_btn.click()
                logging.info("切换到账号密码登录")
                time.sleep(1)
            except:
                logging.warning("未找到切换按钮，可能已默认显示账号密码登录")

            # 输入账号密码
            username_input = WebDriverWait(self.driver, 10).until(
                EC.presence_of_element_located((By.ID, 'lUsername'))
            )
            password_input = WebDriverWait(self.driver, 10).until(
                EC.presence_of_element_located((By.ID, 'lPassword'))
            )

            username_input.send_keys(self.username)
            logging.info(f"输入账号: {self.username}")
            time.sleep(random.uniform(0.5, 1.5))  # 随机延迟，模拟人工操作

            password_input.send_keys(self.password)
            logging.info("输入密码")
            time.sleep(random.uniform(0.5, 1.5))

            # 点击登录按钮
            login_btn = WebDriverWait(self.driver, 10).until(
                EC.element_to_be_clickable((By.CLASS_NAME, 'wall-sub-btn'))
            )
            login_btn.click()
            logging.info("点击登录按钮")

            # 等待登录成功，检查是否跳转到课程页面
            try:
                WebDriverWait(self.driver, 15).until(
                    lambda driver: "study.zhihuishu.com" in driver.current_url or
                                   "onlineservice.zhihuishu.com" in driver.current_url
                )
                logging.info("登录成功")
                return True
            except TimeoutException:
                logging.error("登录超时或失败，请检查账号密码")
                return False

        except Exception as e:
            logging.error(f"登录过程发生错误: {e}")
            return False

    def select_course(self):
        max_retries = 3  # 最大重试次数
        retry_delay = 5  # 每次重试间隔5秒
        for retry in range(max_retries):
            try:
                # 通过 href 属性定位"我的学堂"链接
                login_btn = WebDriverWait(self.driver, 10).until(
                    EC.element_to_be_clickable((By.XPATH, "//a[@href='https://onlineweb.zhihuishu.com']"))
                )
                login_btn.click()
                logging.info("点击我的学堂")

                # 等待课程列表加载
                course_elements = WebDriverWait(self.driver, 15).until(
                    EC.presence_of_all_elements_located((By.CSS_SELECTOR, '.course_li'))
                )

                if not course_elements:
                    logging.error("未找到课程列表，请确认已选课")
                    return False

                # 选择第course1个课程（或按目标课程筛选逻辑，此处省略相关重复代码）
                first_course = course_elements[course1]
                course_title = first_course.find_element(By.CSS_SELECTOR, '.course_name').text
                self.current_video_title = course_title

                # 点击进入课程
                self.driver.execute_script("arguments[0].scrollIntoView();", first_course)
                time.sleep(1)
                first_course.click()
                logging.info(f"选择课程: {course_title}")

                self._switch_to_active_tab()
                course_btn_elements = WebDriverWait(self.driver, 10).until(
                    EC.presence_of_all_elements_located(
                        (By.XPATH, "//span[@class='must_learn' and text()='【选学】']")
                    )
                )
                if course_btn_elements:
                    course_btn_elements[0].click()
                    logging.info("点击第一个选学课程")
                else:
                    logging.warning("未找到选学课程元素")

                # 等待课程页面加载，增加判断视频播放按钮是否出现
                WebDriverWait(self.driver, 30).until(
                    lambda driver:
                    "hike-teaching-center.polymas.com" in driver.current_url
                    # and  # 直接检查URL
                    # driver.find_elements(By.CSS_SELECTOR, '.bigplayButton.pointer')  # 检查元素存在
                )

                # 处理可能出现的弹窗
                self._handle_popups()
                return True

            except TimeoutException:
                if retry < max_retries - 1:
                    logging.error(f"课程页面加载超时，重试 {retry + 1}/{max_retries}...")
                    time.sleep(retry_delay)
                else:
                    logging.error("课程页面加载超时，达到最大重试次数，可能网络问题或页面结构变更")
                    return False
            except Exception as e:
                logging.error(f"选择课程时发生错误: {e}")
                return False

    def _handle_popups(self):
        """处理可能出现的弹窗"""
        try:
            # 关闭可能出现的弹窗
            close_buttons = self.driver.find_elements(By.CSS_SELECTOR, '.popbtn-cancel, .popbtn-refuse')
            if close_buttons:
                for btn in close_buttons:
                    if btn.is_displayed():
                        btn.click()
                        logging.info("关闭弹窗")
                        time.sleep(1)
        except Exception as e:
            logging.warning(f"处理弹窗时发生错误: {e}")

    def start_learning(self):
        """开始学习，自动播放视频"""
        try:
            # 获取视频列表
            self._get_video_list()

            # 开始播放第一个视频
            if not self._play_first_video():
                logging.error("无法开始播放视频")
                return False

            # 进入自动播放循环
            self._auto_play_loop()

            return True

        except Exception as e:
            logging.error(f"学习过程中发生错误: {e}")
            return False

    def _ensure_video_list_visible(self):
        """确保视频列表区域可见"""
        try:
            # 检查是否需要点击展开按钮
            expand_button = WebDriverWait(self.driver, 5).until(
                EC.element_to_be_clickable((By.CSS_SELECTOR, '.expand-btn'))
            )
            expand_button.click()
            logging.info("点击展开按钮以显示视频列表")
        except TimeoutException:
            logging.info("未找到展开按钮，假设视频列表已可见")
            pass

    def _get_video_list(self):
        """获取视频列表信息（在当前活动标签页中查找）"""
        try:
            # 确保切换到当前活动标签页
            self._switch_to_active_tab()

            # 等待视频项容器加载
            WebDriverWait(self.driver, 10).until(
                EC.presence_of_element_located((By.CSS_SELECTOR, '.source-list.nano'))
            )

            # 获取所有视频项
            video_elements = WebDriverWait(self.driver, 10).until(
                EC.presence_of_all_elements_located((By.CSS_SELECTOR, '.file-item'))
            )

            if not video_elements:
                logging.error("未找到视频项")
                return False

            self.total_videos = len(video_elements)
            logging.info(f"发现 {self.total_videos} 个视频")

            completed_count = 0
            for index, video in enumerate(video_elements, 1):
                try:
                    status_icon = video.find_element(By.CSS_SELECTOR, '.status-box')
                    is_completed = 'complete' in status_icon.get_attribute('class')

                    # 获取视频标题
                    title_element = video.find_element(By.CSS_SELECTOR, '.file-name')
                    video_title = title_element.text.strip()

                    logging.info(f"视频 {index}: {video_title} - 状态: {'已完成' if is_completed else '未完成'}")
                    if is_completed:
                        completed_count += 1
                except NoSuchElementException as e:
                    logging.warning(f"视频 {index} 状态图标或标题缺失: {e}")
                    continue

            self.completed_videos = completed_count
            logging.info(f"已完成 {completed_count}/{self.total_videos} 个视频")
            return True

        except TimeoutException:
            logging.error("视频列表加载超时")
            self._save_debug_info("video_list_timeout")
            return False
        except Exception as e:
            logging.error(f"获取视频列表时发生未知错误: {e}", exc_info=True)
            return False

    def _switch_to_active_tab(self):
        """切换到当前活动的标签页"""
        try:
            # 获取当前所有窗口句柄
            all_handles = self.driver.window_handles

            if not all_handles:
                logging.error("没有可用的标签页")
                return False

            # 获取最新打开的标签页（最后一个句柄）
            latest_handle = all_handles[-1]

            # 切换到最新标签页
            self.driver.switch_to.window(latest_handle)
            logging.info(f"已切换到标签页: {self.driver.title}")
            return True

        except Exception as e:
            logging.error(f"切换标签页失败: {e}")
            return False

    def _play_first_video(self):
        """播放第一个未完成的视频，丢弃原始窗口，只在新标签页中操作"""
        try:
            # 在原始窗口中获取视频列表
            if not self._get_video_list():
                logging.error("无法加载视频列表")
                return False

            if self.completed_videos == self.total_videos:
                logging.info("所有视频已完成")
                return False

            # 查找第一个未完成的视频
            video_to_play = self._find_first_incomplete_video()
            if not video_to_play:
                logging.info("未找到未完成的视频")
                return False

            # 点击视频项触发新标签页
            video_to_play.click()
            logging.info(f"点击播放视频: {self.current_video_title}")
            play_button = WebDriverWait(self.driver, 5).until(
                EC.element_to_be_clickable((By.CSS_SELECTOR, '.bigPlayButton'))
            )
            play_button.click()
            logging.info("已点击播放按钮")

            # # 等待新标签页加载
            # self._wait_for_new_window()
            #
            # # 关闭原始窗口，只保留新标签页
            # self.driver.switch_to.window(self.driver.window_handles[0])  # 切换到新窗口
            # logging.info(f"已切换到新标签页: {self.driver.title}")
            #
            # # 在新标签页中等待视频播放器加载
            # if not self._wait_for_video_player_loaded():
            #     logging.error("视频播放器加载失败")
            #     return False

            # 等待视频播放完成
            self._wait_for_video_completion()

            return True

        except TimeoutException as e:
            logging.error(f"操作超时: {e}")
            self._save_debug_info("play_timeout")
            return False
        except WebDriverException as e:
            logging.error(f"WebDriver 错误: {e}")
            self._save_debug_info("webdriver_error")
            return False
        except Exception as e:
            logging.error(f"播放视频时发生未知错误: {e}", exc_info=True)
            self._save_debug_info("play_error")
            return False

    def _find_first_incomplete_video(self):
        """查找第一个未完成的视频"""
        for video in self.driver.find_elements(By.CSS_SELECTOR, '.file-item'):
            try:
                status_icon = video.find_element(By.CSS_SELECTOR, '.status-box')
                if 'complete' in status_icon.get_attribute('class'):
                    continue
            except NoSuchElementException:
                pass  # 无状态图标视为未完成

            try:
                title_element = video.find_element(By.CSS_SELECTOR, '.file-name')
                self.current_video_title = title_element.text.strip()
                return video
            except NoSuchElementException:
                logging.warning("视频标题元素缺失")
                continue

        return None

    def _wait_for_new_window(self, timeout=10):
        """等待新窗口出现并切换到新窗口"""
        original_handles = self.driver.window_handles

        WebDriverWait(self.driver, timeout).until(
            lambda d: len(d.window_handles) > len(original_handles),
            "新窗口未在超时时间内加载"
        )

        # 获取新窗口句柄
        new_handles = [h for h in self.driver.window_handles if h not in original_handles]
        if not new_handles:
            raise TimeoutException("未找到新窗口")

        # 关闭所有原始窗口
        for handle in original_handles:
            self.driver.switch_to.window(handle)
            self.driver.close()

        # 切换到新窗口
        self.driver.switch_to.window(new_handles[0])

    def _wait_for_video_player_loaded(self, timeout=20):
        """等待视频播放器加载完成"""
        try:
            WebDriverWait(self.driver, timeout).until(
                EC.presence_of_element_located((By.CSS_SELECTOR, '.video-player'))
            )
            logging.info("视频播放器已加载")
            return True
        except TimeoutException:
            logging.error("视频播放器加载超时")
            return False

    def _wait_for_video_completion(self):
        """等待视频播放完成"""
        try:
            logging.info("开始等待视频播放完成...")

            # 获取视频元素
            video = WebDriverWait(self.driver, 10).until(
                EC.presence_of_element_located((By.TAG_NAME, 'video'))
            )

            # 获取视频总时长
            duration = self.driver.execute_script("return arguments[0].duration", video)
            logging.info(f"视频总时长: {duration:.2f} 秒")

            # 等待视频播放完成
            WebDriverWait(self.driver, duration + 60).until(  # 添加60秒缓冲时间
                lambda d: self._is_video_complete()
            )

            logging.info("视频播放已完成")

        except TimeoutException:
            logging.warning("等待视频完成超时，检查视频是否已完成")
            if self._is_video_complete():
                logging.info("视频实际已完成")
            else:
                logging.warning("视频未完成，可能需要手动检查")

    def _is_video_complete(self):
        """检查视频是否已完成"""
        try:
            video = self.driver.find_element(By.TAG_NAME, 'video')
            return self.driver.execute_script(
                "return arguments[0].ended || (arguments[0].currentTime >= arguments[0].duration - 2)",
                video
            )
        except Exception as e:
            logging.warning(f"检查视频状态失败: {e}")
            return False

    def _save_debug_info(self, prefix):
        """保存调试信息（截图和页面源码）"""
        try:
            timestamp = time.strftime("%Y%m%d_%H%M%S")
            self.driver.save_screenshot(f"{prefix}_{timestamp}.png")
            with open(f"{prefix}_{timestamp}.html", "w", encoding="utf-8") as f:
                f.write(self.driver.page_source)
            logging.info(f"已保存调试信息: {prefix}_{timestamp}")
        except Exception as e:
            logging.warning(f"保存调试信息失败: {e}")

    def cleanup(self):
        """清理资源，关闭所有窗口"""
        try:
            self.driver.quit()
            logging.info("浏览器已关闭")
        except Exception as e:
            logging.warning(f"关闭浏览器失败: {e}")

    def _ensure_video_playing(self):
        """确保视频正在播放"""
        try:
            # 检查视频元素是否存在并已加载
            video = WebDriverWait(self.driver, 10).until(
                EC.presence_of_element_located((By.TAG_NAME, 'video'))
            )

            # 等待视频开始播放（检查currentTime属性是否变化）
            initial_time = self.driver.execute_script("return arguments[0].currentTime", video)
            WebDriverWait(self.driver, 10).until(
                lambda d: self.driver.execute_script("return arguments[0].currentTime", video) > initial_time
            )

            logging.info("视频正在播放")

        except TimeoutException:
            logging.warning("视频未开始播放，尝试点击播放按钮")
            try:
                play_button = WebDriverWait(self.driver, 5).until(
                    EC.element_to_be_clickable((By.CSS_SELECTOR, '.bigPlayButton'))
                )
                play_button.click()
                logging.info("已点击播放按钮")
            except Exception as e:
                logging.error(f"无法点击播放按钮: {e}")

    def _adjust_volume(self):
        """调整音量（避免静音）"""
        try:
            # 查找音量控制按钮
            volume_button = WebDriverWait(self.driver, 5).until(
                EC.element_to_be_clickable((By.CSS_SELECTOR, '.vjs-mute-control'))
            )

            # 检查是否静音
            is_muted = self.driver.execute_script("return document.querySelector('video').muted;")
            if is_muted:
                volume_button.click()
                logging.info("取消静音")
        except:
            pass

    def _auto_play_loop(self):
        """自动播放循环，检测当前视频是否完成并播放下一个"""
        while True:
            try:
                # 检查当前视频是否完成
                if self._is_video_completed():
                    self.completed_videos += 1
                    logging.info(f"视频已完成！当前进度: {self.completed_videos}/{self.total_videos}")

                    # 点击下一个视频
                    if not self._play_next_video():
                        logging.info("所有视频已播放完毕！")
                        break

                    # 等待下一个视频加载
                    time.sleep(5)

                    # 确保视频开始播放
                    self._ensure_video_playing()
                else:
                    # 视频未完成，继续等待
                    remaining_time = self._get_video_remaining_time()
                    logging.info(f"视频播放中，剩余时间: {remaining_time}")
                    time_to_wait = min(30, max(5, remaining_time // 3))  # 动态调整检查间隔
                    time.sleep(time_to_wait)

            except KeyboardInterrupt:
                logging.info("用户手动中断程序")
                break
            except Exception as e:
                logging.error(f"自动播放循环发生错误: {e}")
                time.sleep(10)  # 发生错误时等待较长时间

    def _is_video_completed(self):
        """检查当前视频是否完成"""
        try:
            # 方法1：通过视频元素判断
            video_element = WebDriverWait(self.driver, 5).until(
                EC.presence_of_element_located((By.TAG_NAME, 'video'))
            )

            # 获取视频总时长和当前播放时间
            duration = self.driver.execute_script("return arguments[0].duration;", video_element)
            current_time = self.driver.execute_script("return arguments[0].currentTime;", video_element)

            # 如果视频时长为0，可能还在加载
            if duration <= 0:
                return False

            # 视频播放完毕
            if current_time >= duration - 1:  # 允许1秒误差
                return True

            # 方法2：检查是否有"继续学习"按钮（智慧树有时会在视频结束后显示此按钮）
            try:
                continue_button = self.driver.find_element(By.CSS_SELECTOR, '.continue-btn')
                if continue_button.is_displayed():
                    continue_button.click()
                    logging.info("切换下一节网课")
                    play_button = WebDriverWait(self.driver, 5).until(
                        EC.element_to_be_clickable((By.CSS_SELECTOR, '.bigPlayButton'))
                    )
                    play_button.click()
                    logging.info("已点击播放按钮")
                    return True
            except:
                pass

            return False

        except Exception as e:
            logging.warning(f"检查视频完成状态时发生错误: {e}")
            return False

    def _get_video_remaining_time(self):
        """获取视频剩余时间（秒）"""
        try:
            video_element = WebDriverWait(self.driver, 5).until(
                EC.presence_of_element_located((By.TAG_NAME, 'video'))
            )

            duration = self.driver.execute_script("return arguments[0].duration;", video_element)
            current_time = self.driver.execute_script("return arguments[0].currentTime;", video_element)

            remaining = max(0, duration - current_time)
            return remaining

        except Exception as e:
            logging.warning(f"获取视频剩余时间时发生错误: {e}")
            return 60  # 默认返回60秒

    def _play_next_video(self):
        """播放下一个视频"""
        try:  # 查找当前播放的视频
            current_video = None
            video_elements = WebDriverWait(self.driver, 10).until(
                EC.presence_of_all_elements_located((By.CSS_SELECTOR, '.file-item'))
            )

            for i, video in enumerate(video_elements):
                try:
                    # 当前播放的视频通常有高亮样式
                    if 'active' in video.get_attribute('class'):
                        current_video = video
                        current_index = i
                        break
                except:
                    continue

            if not current_video:
                # 如果找不到当前视频，尝试找到第一个未完成的视频
                for i, video in enumerate(video_elements):
                    try:
                        status_icon = video.find_element(By.CSS_SELECTOR, '.status-box .icon')
                        if 'finish' not in status_icon.get_attribute('class'):
                            current_video = video
                            current_index = i
                            break
                    except:
                        current_video = video
                        current_index = i
                        break

            if not current_video:
                logging.error("找不到当前视频或下一个视频")
                return False

            # 查找下一个视频
            next_index = current_index + 1
            if next_index >= len(video_elements):
                logging.info("没有下一个视频了")
                return False

            next_video = video_elements[next_index]
            next_title = next_video.find_element(By.CSS_SELECTOR, '.file-name').text
            self.current_video_title = next_title

            # 滚动到下一个视频并点击
            self.driver.execute_script("arguments[0].scrollIntoView();", next_video)
            time.sleep(1)
            next_video.click()
            logging.info(f"开始播放下一个视频: {next_title}")
            play_button = WebDriverWait(self.driver, 8).until(
                EC.element_to_be_clickable((By.CSS_SELECTOR, '.bigPlayButton'))
            )
            play_button.click()
            logging.info("已点击播放按钮")

            return True

        except Exception as e:
            logging.error(f"播放下一个视频时发生错误: {e}")
            return False

    def close(self):
        """关闭浏览器"""
        if self.driver:
            self.driver.quit()
            logging.info("浏览器已关闭")

    def _save_screenshot(self, param):
        pass


def main():
    # 配置信息（请替换为你的信息）
    USERNAME = input("输入你的智慧树账号：")  # 智慧树账号
    PASSWORD = input("输入你的智慧树密码：")  # 智慧树密码
    course1 = int(input("输入你要刷的课程序号："))

    # 创建自动播放器实例
    player = ZhihuishuAutoPlayer(USERNAME, PASSWORD)

    try:
        # 初始化并登录
        player.init_driver()
        if player.login():
            # 选择课程并开始学习
            if player.select_course():
                player.start_learning()
    except Exception as e:
        logging.error(f"程序运行过程中发生严重错误: {e}")
    finally:
        # 关闭浏览器
        player.close()


if __name__ == "__main__":
    main()
